package com.hqd.ch03.v51.io;

import com.hqd.ch03.utils.CollectionUtils;
import com.hqd.ch03.v51.io.support.EncodedResource;
import com.hqd.ch03.v51.io.support.PropertiesLoaderUtils;
import com.hqd.ch03.v51.io.support.ResourcePropertiesPersister;
import com.hqd.ch03.v51.utils.PropertiesPersister;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Properties;

public abstract class PropertiesLoaderSupport {

    /**
     * Logger available to subclasses.
     */
    protected final Log logger = LogFactory.getLog(getClass());


    protected Properties[] localProperties;

    protected boolean localOverride = false;


    private Resource[] locations;

    private boolean ignoreResourceNotFound = false;


    private String fileEncoding;

    private PropertiesPersister propertiesPersister = ResourcePropertiesPersister.INSTANCE;


    /**
     * Set local properties, e.g. via the "props" tag in XML bean definitions.
     * These can be considered defaults, to be overridden by properties
     * loaded from files.
     */
    public void setProperties(Properties properties) {
        this.localProperties = new Properties[]{properties};
    }

    /**
     * Set local properties, e.g. via the "props" tag in XML bean definitions,
     * allowing for merging multiple properties sets into one.
     */
    public void setPropertiesArray(Properties... propertiesArray) {
        this.localProperties = propertiesArray;
    }

    /**
     * Set a location of a properties file to be loaded.
     * <p>Can point to a classic properties file or to an XML file
     * that follows JDK 1.5's properties XML format.
     */
    public void setLocation(Resource location) {
        this.locations = new Resource[]{location};
    }

    /**
     * Set locations of properties files to be loaded.
     * <p>Can point to classic properties files or to XML files
     * that follow JDK 1.5's properties XML format.
     * <p>Note: Properties defined in later files will override
     * properties defined earlier files, in case of overlapping keys.
     * Hence, make sure that the most specific files are the last
     * ones in the given list of locations.
     */
    public void setLocations(Resource... locations) {
        this.locations = locations;
    }

    /**
     * Set whether local properties override properties from files.
     * <p>Default is "false": Properties from files override local defaults.
     * Can be switched to "true" to let local properties override defaults
     * from files.
     */
    public void setLocalOverride(boolean localOverride) {
        this.localOverride = localOverride;
    }

    /**
     * Set if failure to find the property resource should be ignored.
     * <p>"true" is appropriate if the properties file is completely optional.
     * Default is "false".
     */
    public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
        this.ignoreResourceNotFound = ignoreResourceNotFound;
    }

    public void setFileEncoding(String encoding) {
        this.fileEncoding = encoding;
    }

    /**
     * Set the PropertiesPersister to use for parsing properties files.
     * The default is ResourcePropertiesPersister.
     *
     * @see ResourcePropertiesPersister#INSTANCE
     */
    public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
        this.propertiesPersister =
                (propertiesPersister != null ? propertiesPersister : ResourcePropertiesPersister.INSTANCE);
    }


    /**
     * Return a merged Properties instance containing both the
     * loaded properties and properties set on this FactoryBean.
     */
    protected Properties mergeProperties() throws IOException {
        Properties result = new Properties();

        if (this.localOverride) {
            // Load properties from file upfront, to let local properties override.
            loadProperties(result);
        }

        if (this.localProperties != null) {
            for (Properties localProp : this.localProperties) {
                CollectionUtils.mergePropertiesIntoMap(localProp, result);
            }
        }

        if (!this.localOverride) {
            // Load properties from file afterwards, to let those properties override.
            loadProperties(result);
        }

        return result;
    }

    /**
     * Load properties into the given instance.
     *
     * @param props the Properties instance to load into
     * @throws IOException in case of I/O errors
     * @see #setLocations
     */
    protected void loadProperties(Properties props) throws IOException {
        if (this.locations != null) {
            for (Resource location : this.locations) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Loading properties file from " + location);
                }
                try {
                    PropertiesLoaderUtils.fillProperties(
                            props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
                } catch (FileNotFoundException | UnknownHostException | SocketException ex) {
                    if (this.ignoreResourceNotFound) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Properties resource not found: " + ex.getMessage());
                        }
                    } else {
                        throw ex;
                    }
                }
            }
        }
    }

}
